# -*- coding: utf-8 -*-
import os
from struct import pack,unpack
import sys
import socket
import copy
import types
base = [str(x) for x in range(10)] + [ chr(x) for x in range(ord('A'),ord('A')+6)]
def dec2hex(string_num):
    num = int(string_num)
    mid = []
    while True:
        if num == 0: break
        num,rem = divmod(num, 16)
        mid.append(base[rem])

    return ''.join([str(x) for x in mid[::-1]])

#定义数据类型
_BSP_VAL_INT8 = 0x1
_BSP_VAL_INT16 = 0x2
_BSP_VAL_INT32 = 0x3
_BSP_VAL_INT64 = 0x4
_BSP_VAL_FLOAT = 0x11
_BSP_VAL_DOUBLE = 0x12
_BSP_VAL_STRING = 0x21
#带下标字典 dict
_BSP_VAL_OBJECT = 0x51
_BSP_VAL_OBJECT_END = 0x52
#不带下标 list
_BSP_VAL_ARRAY = 0x61
_BSP_VAL_ARRAY_END = 0x62
_BSP_VAL_NULL = 0x7F

#定义网络字节序
_BSP_ENDIAN_LITTLE =  0x0
_BSP_ENDIAN_BIG = 0x1

#int长度类型定义
_PACKET_MARK_32B = 0x0
_PACKET_MARK_64B = 0x1

#数据类型是：
_PACKET_TYPE_REP = 0x0
_PACKET_TYPE_RAW = 0x1
_PACKET_TYPE_OBJ = 0x2
_PACKET_TYPE_CMD = 0x3
_PACKET_TYPE_HEARTBEAT = 0x7
#压缩类型
_COMPRESS_TYPE_NONE = 0x0
_COMPRESS_TYPE_DEFLATE = 0x1
_COMPRESS_TYPE_LZO = 0x2
_COMPRESS_TYPE_SNAPPY = 0x3
#长度类型标记
#_PACKET_MARK_32B = 0x0
#_PACKET_MARK_64B = 0x1

class BspPacket:
	def __init__(self,mark_type = _PACKET_MARK_32B):
		#获取当前平台的字节序
		if sys.byteorder == "little" :
			self.endian = _BSP_ENDIAN_LITTLE
		else:
			self.endian = _BSP_ENDIAN_BIG

		#根据参数设置当前获取数据的长度 是int 还是 long
		if 	mark_type == _PACKET_MARK_32B:
			self.mark_type = _PACKET_MARK_32B
		else:
			self.mark_type = _PACKET_MARK_64B

		self.str = ""
		self.hash_obj = {}
		self.hash_array = []
		#self.offset = 0 
		#print len(str(sys.maxint))
	#获取数据类型长度
	def _type(self,val):
		temp = -1 
		if type(val) == types.IntType:
			#int
			r_val = int(val)
			if (r_val >> 32) > 0 :
				temp = BSP_VAL_INT64
			elif (r_val >> 16) > 0:
				temp = BSP_VAL_INT32
			elif (r_val >> 8) > 0:
				temp = BSP_VAL_INT16
			else:
				temp = BSP_VAL_INT8

		elif type(val) == types.FloatType:
			#float
			temp = _BSP_VAL_FLOAT
		elif type(val) == types.LongType:
			#Double
			temp = _BSP_VAL_DOUBLE
		elif type(val) == types.StringType:
			#string
			temp =  _BSP_VAL_STRING
		elif type(val) == types.DictType:
			#dict
			temp = _BSP_VAL_OBJECT
		elif type(val) == types.ListType:
			#list
			temp = _BSP_VAL_ARRAY
		elif type(val) == types.NoneType:
			#null
			temp = _BSP_VAL_NULL
		return temp
	def _get_int8(self):
		val = 0
		if len(self.str) >0:
			val = unpack('!B',self.str[0:1])[0]
			self.str = self.str[1:]
		return val
		
	def _get_int16(self):
		val = 0
		if len(self.str) >1 :
			val = unpack('!h',self.str[0:2])[0]
			#val = ord(self.str[self.offset]) + ord(self.str[self.offset+1])
			#if val > 0x7FFF:
			#	val = val - 0x10000
		#self.offset = self.offset + 2
			self.str = self.str[2:]
		return val
	def _get_int32(self):
		val = 0
		if len(self.str) >3 :
			val = unpack('!i',self.str[0:4])[0]
			#val = ord(self.str[self.offset]) << 24 + ord(self.str[self.offset+1]) << 16 + ord(self.str[self.offset+2]) << 8 + ord(self.str[self.offset+3])
			#if val > 0x7FFFFFFF:
			#	val = val - 0x100000000
		#self.offset = self.offset + 4
			self.str = self.str[4:]
		return val
	def _get_int64(self):
		val = unpack('!Q',self.str[self.offset:(self.offset+8)])[0]
		#self.offset = self.offset + 8
		self.str = self.str[8:]
		return val
	def _get_float(self):
		val = unpack('!f',self.str[self.offset:(self.offset+4)])[0]
		#self.offset = self.offset + 4
		self.str = self.str[4:]
		return val
	def _get_double(self):
		val = unpack('!d',self.str[self.offset:(self.offset+8)])[0]
		#self.offset = self.offset + 8
		self.str = self.str[8:]
		return val
	def _get_string(self):
		val = ""
		str_len = 0
		if self.mark_type == _PACKET_MARK_32B:
			str_len = self._get_int32()
		else:
			str_len = self._get_int64()
		pf = "%ds" % str_len
		val = unpack(pf,self.str[0:str_len])[0]
		#self.offset = self.offset + str_len
		self.str = self.str[str_len:]
		return val
	def _pack_object_get_key(self):
		val = ""
		value_type = self._get_int8()
		if value_type == _BSP_VAL_STRING:
			val = self._get_string()
		return val
	def _value(self):
		value_type = self._get_int8()
		#print dec2hex(value_type)
		#sys.exit(0)
		temp = ""
		if value_type == _BSP_VAL_INT8:
			temp = self._get_int8()
		elif value_type == _BSP_VAL_INT16:
			temp = self._get_int16()
		elif value_type == _BSP_VAL_INT32:
			temp = self._get_int32()
		elif value_type == _BSP_VAL_INT64:
			temp = self._get_int64()
		elif value_type == _BSP_VAL_FLOAT:
			temp = self._get_float()
		elif value_type == _BSP_VAL_DOUBLE:
			temp = self._get_double()
		elif value_type == _BSP_VAL_STRING:
			temp = self._get_string()
		elif value_type == _BSP_VAL_NULL:
			temp = None
			#self.str = self.str[1:]
		elif value_type == _BSP_VAL_OBJECT:
			temp = "{"
			#self.str = self.str[1:]
		elif value_type == _BSP_VAL_OBJECT_END:
			temp = "}"
			#self.str = self.str[1:]
		elif value_type == _BSP_VAL_ARRAY:
			temp = "["
			#self.str = self.str[1:]
		elif value_type == _BSP_VAL_ARRAY_END:
			temp = "]"
			#self.str = self.str[1:]
		return temp

	def _get_object(self,val,key_list):
		cur_obj = val
		for i in key_list:
			#print cur_obj
			cur_obj = cur_obj[i]
		while len(self.str)>0:
			temp_key = self._value()
			if temp_key == "}" :
				key_list.pop()
				return cur_obj
			else:
				temp_value = self._value()
				if temp_value == "{":
					cur_obj[temp_key] = {}
					key_list.append(temp_key)
					self._get_object(val,key_list)
				elif temp_value == "[" :
					cur_obj[temp_key] = []
					key_list.append(temp_key)
					self._get_list(val,key_list)
				else:
					cur_obj[temp_key] = temp_value
	def _get_list(self,val,key_list):
		cur_obj = val
		for i in key_list:
			if type(cur_obj) == types.ListType:
				cur_obj = cur_obj[i]
				#print cur_obj
			else:
				cur_obj = cur_obj[i]
		while len(self.str)>0:
			temp_key = self._value()
			#print temp_key
			if temp_key == "]" :
				key_list.pop()
				return cur_obj
			else:
				if temp_key == "[":
					#temp_value = self._value()
					#print key_list
					key_list.append(len(cur_obj))
					cur_obj.append([])
					#cur_obj.extend([temp_value])
					self._get_list(val,key_list)
				elif temp_key == "{":
					key_list.append(len(cur_obj))
					cur_obj.append({})
					#print key_list
					#sys.exit(0)
					self._get_object(val,key_list)
				else:
					cur_obj.append(temp_key)

	def bsp_unpack(self,input):
		self.str = input
		val = {}
		while len(self.str)>0:
			obj_key = self._value()
			temp_value = self._value()
			if temp_value == "{":
				val[obj_key] = {}
				self._get_object(val,[obj_key])
			elif temp_value == "[":
				val[obj_key] = []
				self._get_list(val,[obj_key])
			else:
				val[obj_key] = temp_value
		#print val
		return val
	def pack_int(self,input_val):
		r_val = int(input_val)
		if (r_val >> 32) > 0 :
			return pack("B",_BSP_VAL_INT64) + pack("!q",r_val)
		elif (r_val >> 16) > 0:
			return pack("B",_BSP_VAL_INT32) + pack("!i",r_val)
		elif (r_val >> 8) > 0:
			return pack("B",_BSP_VAL_INT16) + pack("!h",r_val)
		else:
			return pack("B",_BSP_VAL_INT8) + pack("!b",r_val)
		return ""
	def pack_float(self,val):
		r_val = float(val)
		return pack("B",_BSP_VAL_FLOAT) + pack("!f",r_val)
	def pack_double(self,val):
		r_val = double(val)
		return pack("B",_BSP_VAL_DOUBLE) + pack("!d",r_val)
	def pack_null(self,val):
		return pack("B",_BSP_VAL_NULL)
	def pack_string(self,val):
		r_val = str(val)
		r_val_len = len(r_val)
		pf = "%ds" % r_val_len
		return pack("B",_BSP_VAL_STRING) + pack("!i",r_val_len) + pack(pf,r_val)
	def pack_list(self,list_body):
		pack_list_str = ""
		if type(list_body) == types.ListType:
			pack_list_str += pack("!B",_BSP_VAL_ARRAY)
			for value in list_body:
				if type(value) == types.IntType:
					pack_list_str += self.pack_int(value)
				elif type(value) == types.FloatType:
					pack_list_str += self.pack_float(value)
				elif type(value) == types.LongType:
					pack_list_str += self.pack_double(value)
				elif type(value) ==  types.NoneType:
					pack_list_str += self.pack_null(value)
				elif type(value) == types.StringType:
					pack_list_str += self.pack_string(value)
				elif type(value) == types.DictType:
					pack_list_str += self.pack_dict(value,False)
				elif type(value) == types.ListType:
					pack_list_str += self.pack_list(value)
			pack_list_str += pack("!B",_BSP_VAL_ARRAY_END)
		return pack_list_str
	def pack_dict(self,dict_body,is_one=True):
		if type(dict_body) != types.DictType:
			print "input value is not dict!";
			return ""
		else:
			pack_dict_str = ""
			if is_one == False:
				pack_dict_str += pack("!B",_BSP_VAL_OBJECT)
			for key,value in dict_body.items():
				pack_dict_str += self.pack_string(str(key))
				if type(value) == types.IntType:
					pack_dict_str += self.pack_int(value)
				elif type(value) == types.FloatType:
					pack_dict_str += self.pack_float(value)
				elif type(value) == types.LongType:
					pack_dict_str += self.pack_double(value)
				elif type(value) ==  types.NoneType:
					pack_dict_str += self.pack_null(value)
				elif type(value) == types.StringType:
					pack_dict_str += self.pack_string(value)
				elif type(value) == types.DictType:
					pack_dict_str += self.pack_dict(value,False)
				elif type(value) == types.ListType:
					pack_dict_str += self.pack_list(value)
			if is_one == False:
				pack_dict_str += pack("!B",_BSP_VAL_OBJECT_END)
			return pack_dict_str


class BspComp:
	def __init__(self,comp_type):
		self.comp_type = comp_type
		if self.comp_type == _COMPRESS_TYPE_DEFLATE:
			import zlib
		elif self.comp_type == _COMPRESS_TYPE_LZO:
			pass
		elif self.comp_type == _COMPRESS_TYPE_SNAPPY:
			pass
	def bspcomp(self,comp_str):
		if self.comp_type == _COMPRESS_TYPE_NONE:
			return comp_str
		elif self.comp_type == _COMPRESS_TYPE_DEFLATE:
			return zlib.compress(comp_str)
		else:
			return comp_str
	def bspuncomp(self,uncomp_str):
		if self.comp_type == _COMPRESS_TYPE_NONE:
			return uncomp_str
		elif self.comp_type == _COMPRESS_TYPE_DEFLATE:
			return zlib.decompress(comp_str)
		else:
			return uncomp_str
class BspClient:
	def __init__(self,comp_type = _COMPRESS_TYPE_NONE,mark_type = _PACKET_MARK_32B,is_ipv6=False,socket_type="tcp"):
		self.mark_type = mark_type
		self.comp_type = comp_type
		self.bsp_comp = BspComp(self.comp_type)
		self.pack_dict_str = ""
		self.is_ipv6 = is_ipv6
		self.socket_type = socket_type
		self.bsp = BspPacket()
	def connect(self,host,port):
		'''当前只提供tcp协议'''
		if self.is_ipv6 == True:
			ip_type = socket.AF_INET6
		else:
			ip_type = socket.AF_INET
		if self.socket_type == "tcp":
			socket_type = socket.SOCK_STREAM
		else:
			socket_type = socket.SOCK_DGRAM
		cs=socket.socket(ip_type, socket_type)
		cs.settimeout(10)
		cs.connect((host, port))
		self.socket = cs
	def bulid_head(self,pack_type=_PACKET_TYPE_CMD):
		if pack_type == _PACKET_TYPE_HEARTBEAT :
			hdr = 224
		else:
			hdr = ((pack_type & 0b111) << 5) | ((self.comp_type & 0b111) << 2) | (self.mark_type & 0b11)
		#print hdr
		return pack("!B",hdr)
	def set_rep(self,comp_type,mark_type):
		self.mark_type = mark_type
		self.comp_type = comp_type
		self.bsp_comp = BspComp(self.comp_type)
		send_content = self.bulid_head(_PACKET_TYPE_REP)
		send_len = self.socket.send(send_content)
		recv_content = self.socket.recv(send_len)
		temp = False
		if send_content == recv_content:
			temp = True
		return temp
	def ping(self):
		send_content = self.bulid_head(_PACKET_TYPE_REP)
		send_len = self.socket.send(send_content)
		recv_content = self.socket.recv(send_len)
		temp = False
		if send_content == recv_content:
			temp = True
		return temp
	def send_raw(self,body):
		send_content = self.bulid_head(_PACKET_TYPE_RAW)
		body = self.bsp_comp.bspcomp(body)
		str_len = len(body)
		if self.mark_type == _PACKET_MARK_32B:
			send_content +=  pack("!i",str_len)
		else:
			send_content +=  pack("!q",str_len)
		send_content += body
		return self.socket.send(send_content)
	
	def send_obj(self,dict_body):
		send_content = self.bulid_head(_PACKET_TYPE_OBJ)
		body = self.bsp.pack_dict(dict_body,True)
		#body = pack("!i",cmd)+body
		body = self.bsp_comp.bspcomp(body)
		str_len = len(body)
		if self.mark_type == _PACKET_MARK_32B:
			send_content +=  pack("!i",str_len)
		else:
			send_content +=  pack("!q",str_len)
		send_content += body
		return self.socket.send(send_content)
	def send_cmd(self,cmd,dict_body):
		send_content = self.bulid_head(_PACKET_TYPE_CMD)
		body = self.bsp.pack_dict(dict_body,True)
		body = pack("!I",cmd)+body
		body = self.bsp_comp.bspcomp(body)
		str_len = len(body)
		if self.mark_type == _PACKET_MARK_32B:
			send_content +=  pack("!I",str_len)
		else:
			send_content +=  pack("!Q",str_len)
		send_content += body
		return self.socket.send(send_content)

	def recv_data(self):
		'''这里不处理rep包跟心跳包'''
		header = self.socket.recv(1)
		hdr = unpack("!B",header)[0]
		#包类型
		pt = (hdr >> 5) & 0b111
		#压缩类型
		pc = (hdr >> 2) & 0b111
		#包长度的int类型
		pm = hdr & 0b11
		#获取数据长度
		if pm == _PACKET_MARK_32B:
			temp = self.socket.recv(4)
			content_len = unpack("!i",temp)[0]
		else:
			temp = self.socket.recv(8)
			content_len = unpack("!q",temp)[0]
		body = ""
		while len(body) < content_len:
			body += self.socket.recv(content_len - len(body))
		bsp_comp = BspComp(pc)
		body = bsp_comp.bspuncomp(body)
		if pt == _PACKET_TYPE_RAW:
			return {"body":body}
		elif pt == _PACKET_TYPE_OBJ:
			body = self.bsp.bsp_unpack(body)
			return {"body":body}
		elif pt == _PACKET_TYPE_CMD:
			cmd = unpack("!i",body[0:4])[0]
			body = body[4:]
			body = self.bsp.bsp_unpack(body)
			return {"cmd":cmd,"body":body}
		else:
			return {}
#bsp = BspPacket()
if __name__ == '__main__':
	host = "192.168.56.208"
	port = 8254

	client = BspClient()
	client.connect(host,port)
	#print client.send_rep()
	#print client.send_heartbeat()
	#client.recv_data()
	#client.send_obj({"key":"sanshi","key2":{"a":"1"},"key3":['a','c','b']})
	#print client.send_cmd(1001,{"key":{"a1":["sanshi"]},"key2":{"a":"1"},"key3":['a','c','b']})
	#client.send_cmd(1001,{"k":[1,2,3,{'a':1,'b':'sanshi','c':{'c1':'c1','c2':'c2'}}]})
	client.send_cmd(1001,{"k":{'a':[1,2,3,['a','b']]}})
	#print client.send_raw("sanshi")
	print client.recv_data()
	sys.exit()
	

	cs=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	cs.settimeout(10)
	cs.connect((host, port))
	send_head = pack("B",)
	send_cmd = pack("B",1)
	send_body = pack("BB",0x1,10)
	print len(send_body)
	cs.send(send_cmd+pack("i",len(send_body))+send_body)
